﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using Ajaxna.Designers;
using System.ComponentModel.Design;
using System.Drawing.Design;
using System.Reflection;

namespace Ajaxna
{    
    /// <summary>
    /// This class consists the Ajaxna Control itself. A Composite control.
    /// </summary>
    [ParseChildren(true),  Designer(typeof(AjaxnaControlDesigner)),
    ToolboxData("<{0}:AjaxnaControl runat=server></{0}:AjaxnaControl>"),
    NonVisualControl(true)]
    public class AjaxnaControl : CompositeControl
    {                
        public static AjaxnaControl GetCurrent(Page page)
        {
            if (page == null)
                throw new ArgumentNullException("page");

            // Get the AjaxnaControl from the page's items collection.
            return page.Items[typeof(AjaxnaControl)] as AjaxnaControl;
        }

        protected override void OnInit(EventArgs e)
        {
            if (!base.DesignMode)
            {
                // add this AjaxnaControl to the page's items collection.
                if (GetCurrent(this.Page) != null)
                    throw new InvalidOperationException("Only one Ajaxna control is allowed on a page");

                this.Page.Items[typeof(AjaxnaControl)] = this;
            }
            base.OnInit(e);
        }

        List<AjaxnaScriptReference> _scripts = new List<AjaxnaScriptReference>();
        string _resourcesName = "Ajaxna.axd";

        [DefaultValue("")]
        [Category("Configuration"), Description("The name of the handler (in your web.config) used to retrive Ajax resources")]
        public string ResourcesName
        {
            get { return _resourcesName; }
            set 
            {                
                value = value.Replace("~", "");
                value = value.Replace("/", "");
                value = value.Replace("?", "");
                _resourcesName = value; 
            }
        }

        [DefaultValue("")]
        [Browsable(false)]
        public string ResourcesPath
        {
            get 
            {                
                return "~/" + _resourcesName + "?path="; 
            }
        }

        /// <summary>
        /// Designed for use by external DLLs wishing to host Ajaxna applications.
        /// This method will return a string that can be used as the "domain" argument in a client side call to $addApplication
        /// when the client wishes to register an external Ajaxna application that resides in an external DLL assembly.
        /// Usage from a server control:        
        /// string clientDomain = AjaxnaControl.GetCurrent(this.Page).GetExternalResourcesPath(this.GetType().Assembly.GetType().Name);
        /// </summary>
        /// <param name="assemblyName"></param>
        /// <returns></returns>
        [Browsable(false)]
        public string GetExternalResourcesPath(string assemblyName)
        {
            return Page.ResolveClientUrl(string.Format("~/{0}?assembly={1}&path=", _resourcesName, assemblyName));
        }

        /// <summary>
        /// Designed for use by external DLLs wishing to host Ajaxna applications.
        /// This method will return a string that can be used as the "domain" argument in a client side call to $addApplication
        /// when the client wishes to register an external Ajaxna application that resides in an external DLL assembly.
        /// Usage from a server control:        
        /// string clientDomain = AjaxnaControl.GetCurrent(this.Page).GetExternalResourcesPath(this.GetType().Assembly);
        /// </summary>
        /// <param name="assembly"></param>
        /// <returns></returns>
        public string GetExternalResourcesPath(Assembly assembly)
        {
            string asmName = assembly.FullName.Split(',')[0];
            return GetExternalResourcesPath(asmName);
        }

        [DefaultValue("")]
        [Category("Configuration"), Description("Add any initialization client scripts you may have to ensure they are loaded after the Core Ajaxna library.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [Browsable(true), PersistenceMode(PersistenceMode.InnerProperty), NotifyParentProperty(true)]
        public List<AjaxnaScriptReference> Scripts
        {
            get 
            {                
                return _scripts; 
            }            
        }

        public override void RenderBeginTag(HtmlTextWriter writer)
        {            
        }
        public override void RenderEndTag(HtmlTextWriter writer)
        {            
        }
        

        protected override void CreateChildControls()
        {
            Controls.Clear();                      
        }

        protected override void OnPreRender(EventArgs e)
        {
            string asmName = this.GetType().Assembly.GetName().Name;
            string[] bootStrappers = new string[] 
            {
                "ajax.lang.extensions.js",
                "ajax.net.ContentLoader.js"
            };

            // If the scriptmanager control is present, then use that (they maybe including MS Ajax classes)
            // otherwise just use the ClientScript
            ScriptManager scriptManager = ScriptManager.GetCurrent(Page);


            foreach (string bootStrap in bootStrappers)
                Page.ClientScript.RegisterClientScriptResource(this.GetType(), string.Format("{0}.{1}", asmName, bootStrap));

            // Add in the domain entered.
            string ajaxnaResources = string.Format("ajax.domain = \"{0}\";", Page.ResolveClientUrl(this.ResourcesPath));
            Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "AjaxnaPath", ajaxnaResources, true);

            // Add any Scripts added to this control            
            foreach (AjaxnaScriptReference scriptRef in this.Scripts)
            {
                if (!string.IsNullOrEmpty(scriptRef.Path))
                {// Use Path
                    if (scriptManager != null)
                        scriptManager.Scripts.Add(new ScriptReference(scriptRef.Path));
                    else
                    {
                        string resolvedPath = this.ResolveClientUrl(scriptRef.Path);
                        Page.ClientScript.RegisterClientScriptInclude(resolvedPath, resolvedPath);
                    }
                }
                else if (!string.IsNullOrEmpty(scriptRef.Name) && string.IsNullOrEmpty(scriptRef.Assembly))
                {// Use resource from this DLL
                    if (scriptManager != null)
                        scriptManager.Scripts.Add(new ScriptReference(scriptRef.Name, scriptRef.Assembly));
                    else
                        Page.ClientScript.RegisterClientScriptResource(this.GetType(), scriptRef.Name);
                }
                else if (!string.IsNullOrEmpty(scriptRef.Name) && !string.IsNullOrEmpty(scriptRef.Assembly))
                {// Use resource from foreign DLL
                    if (scriptManager != null)
                        scriptManager.Scripts.Add(new ScriptReference(scriptRef.Name, scriptRef.Assembly));
                    else
                    {
                        Assembly asm = Assembly.Load(scriptRef.Assembly);
                        Type firstType = asm.GetTypes()[0];
                        Page.ClientScript.RegisterClientScriptResource(firstType, scriptRef.Name);
                    }
                }
            }  
            base.OnPreRender(e);
        }

        public override ControlCollection Controls
        {
            get
            {
                EnsureChildControls();
                return base.Controls;
            }
        }


        /// <summary>
        /// Adds a script reference for a local js file
        /// </summary>
        /// <param name="path">Local js file</param>
        public void AddScriptReference(string path)
        {
            AddScriptReference(path, null, "");
        }

        /// <summary>
        /// Adds a script reference for a js file held in an external assembly. 
        /// This is suitable for pre-loading and does not use the dynamic loading mechanism.
        /// Note: Adding Script References is not required if "$new" dynamic loading is used.
        /// Note: You need to call this *before* the OnPreRender event.
        /// Note: The script will need to be marked with the WebResource attribute.
        /// </summary>
        /// <param name="assembly">The assembly holding the resource</param>
        /// <param name="scriptAsmPath">The full asm path to the file.</param>
        public void AddScriptReference(Assembly assembly, string scriptAsmPath)
        {
            AddScriptReference("", assembly, scriptAsmPath);
        }

        /// <summary>
        /// Adds a script reference.
        /// </summary>
        /// <param name="path">The path to a local script file. Pass null or empty string if loading from an assembly.</param>
        /// <param name="assembly">The assembly to load from. null is allowed.</param>
        /// <param name="scriptAsmPath">The full path to the script in asm.</param>
        private void AddScriptReference(string path, Assembly assembly, string scriptAsmPath)
        {
            string asmName = (assembly != null) ? assembly.FullName.Split(',')[0] : "";
            AjaxnaScriptReference sr = new AjaxnaScriptReference(path, scriptAsmPath, asmName);
            Scripts.Add(sr);
        }
        

        public string GetApplicationDescriptor(Assembly assembly, string path)
        {
            string asmName = assembly.FullName.Split(',')[0];
            /* TODO: remove this and change the client code to detect the pattern so that:
             * $import("dllName.has.namespaces.ui.custom.MyControl");
             * would detect that dllName.has.namespaces should be transformed into 
             * /Ajaxna?assembly=DllName.Has.NameSpaces&path=ui.custom.MyControl
             * 
             * Currently I think it will only transform, from namespace to path, when looking at just the FIRST element
             * before a period.
             * */
            asmName = asmName.Replace(".", "$"); 

            // Lowecase the assembly name to make it appear like an Ajaxna namespace
            string clientAsmName = asmName[0].ToString().ToLower() + asmName.Substring(1, asmName.Length - 1);
            string appFormatter = "$addApplication(\"{0}\", \"{1}\", \"{2}\");";
            string script = string.Format(appFormatter, clientAsmName, path, this.GetExternalResourcesPath(assembly));
            return script;
        }


        /// <summary>
        /// Adds a client side application registration to point to the root of your external assembly.
        /// </summary>
        /// <param name="control">The control that is registering the Client Script Block</param>
        /// <param name="assembly">The Assembly to register as a dynamic source of client classes</param>
        public void AddApplicationDescriptor(Control control, Assembly assembly)
        {
            AddApplicationDescriptor(control, assembly, "");
        }

        /// <summary>
        /// Adds a client side application registration to point to any path in your external assembly.
        /// </summary>
        /// <param name="control">The control that is registering the Client Script Block</param>
        /// <param name="assembly">The Assembly to register as a dynamic source of client classes</param>
        /// <param name="path">An optional path into your assembly to use as root</param>
        public void AddApplicationDescriptor(Control control, Assembly assembly, string path)
        {
            string script = this.GetApplicationDescriptor(assembly, path);
            ScriptManager.RegisterStartupScript(control, control.GetType(), assembly.FullName, script, true);
        }
    }
}
